package org.raymond.iworks.gateway.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.raymond.iworks.commons.constants.CommonConstants;
import org.raymond.iworks.core.config.BaseConfiguration;
import org.raymond.iworks.gateway.authenticate.LoginFilter;
import org.raymond.iworks.gateway.exception.JsonExceptionHandler;
import org.raymond.iworks.gateway.filter.*;
import org.raymond.iworks.gateway.filter.support.DecryptFunction;
import org.raymond.iworks.gateway.filter.support.EncryptFunction;
import org.raymond.iworks.gateway.utils.SignUtil;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.Collections;
import java.util.List;

@Slf4j
@Configuration
@AutoConfigureAfter(value={BaseConfiguration.class})
public class GatewayConfig {

    @Bean
    public CorsConfiguration corsConfiguration(){
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.applyPermitDefaultValues();
        return corsConfiguration;
    }

    @Bean
    @ConditionalOnBean({ModifiedParametersFilter.class, RepeatSubmitFilter.class})
    public RouteLocator routes(RouteLocatorBuilder builder, ObjectMapper objectMapper,
                               ModifiedParametersFilter modifiedParametersFilter,
                               RepeatSubmitFilter repeatSubmitFilter) {
        return builder.routes()
                .route("admin", r->r.path("/admin/**").and().method(HttpMethod.GET, HttpMethod.POST)
                        .filters(f->f.stripPrefix(1).filter(modifiedParametersFilter)//.requestRateLimiter(cfg->{})
                            .modifyRequestBody(String.class, String.class, new DecryptFunction(CommonConstants.ENCRYPT_ENABLE, objectMapper))
                            .modifyResponseBody(String.class, String.class, new EncryptFunction(CommonConstants.ENCRYPT_ENABLE, objectMapper))
                            .filter(repeatSubmitFilter)
                        )
                        //.metadata(RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR, "200")
                        //.metadata(RouteMetadataUtils.CONNECT_TIMEOUT_ATTR, "200")
                        .uri("lb://services"))
                .route("test", r->r.path("/test/**").and().method(HttpMethod.GET, HttpMethod.POST).
                        filters(f->f.stripPrefix(1).filter(modifiedParametersFilter))
                        //.metadata(RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR, "200")
                        //.metadata(RouteMetadataUtils.CONNECT_TIMEOUT_ATTR, "200")
                        .uri("lb://services"))
                .build();
    }

    @Bean
    @ConditionalOnMissingBean(GatewayContextFilter.class)
    public GatewayContextFilter gatewayContextFilter(){
        return new GatewayContextFilter();
    }

    @Bean
    @ConditionalOnMissingBean(ModifiedParametersFilter.class)
    public ModifiedParametersFilter modifiedParametersFilter(){
        return new ModifiedParametersFilter();
    }

    @Bean
    @ConditionalOnMissingBean(TraceFilter.class)
    public TraceFilter traceFilter(){
        return new TraceFilter();
    }

    @Bean
    @ConditionalOnMissingBean(RepeatSubmitFilter.class)
    public RepeatSubmitFilter repeatSubmitFilter(){
        return new RepeatSubmitFilter();
    }

    @Bean
    @ConditionalOnMissingBean(LoginFilter.class)
    public LoginFilter loginFilter(){
        return new LoginFilter();
    }

    @Bean
    @ConditionalOnMissingBean(CleanFilter.class)
    public CleanFilter cleanFilter(){
        return new CleanFilter();
    }

    @Primary
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                                             ServerCodecConfigurer serverCodecConfigurer) {
        JsonExceptionHandler jsonExceptionHandler = new JsonExceptionHandler();
        jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
        jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
        if(log.isDebugEnabled()) {
            log.debug("ErrorWebExceptionHandler {}", jsonExceptionHandler);
        }
        return jsonExceptionHandler;
    }

    @Bean
    public SignUtil signUtil(ObjectMapper objectMapper){
        SignUtil signUtil = new SignUtil();
        signUtil.setObjectMapper(objectMapper);
        return signUtil;
    }
}
